package face.util;

import java.io.File;
import java.nio.IntBuffer;
import java.util.List;

 
import org.bytedeco.javacpp.opencv_core;
import org.bytedeco.javacpp.opencv_imgcodecs;
import org.bytedeco.javacpp.opencv_imgproc;

import com.google.common.collect.Lists;

import org.bytedeco.javacpp.opencv_core.LDA;
import org.bytedeco.javacpp.opencv_core.Mat;
import org.bytedeco.javacpp.opencv_core.MatVector;
import org.bytedeco.javacpp.opencv_core.Range;
import org.bytedeco.javacpp.opencv_core.Size;
import org.bytedeco.javacpp.opencv_face.BasicFaceRecognizer;
import org.bytedeco.javacpp.opencv_face.EigenFaceRecognizer;

import cn.hutool.core.io.file.FileReader;

public class PCAFacePoints {
	
	// 使用CSV文件读取图像和标签，主要使用stringstream和getline方法
	public static List<Object> read_csv(String filename) {
		List<Object> lists = Lists.newArrayList();
		FileReader fileReader = new FileReader(filename);
		MatVector images = new MatVector(fileReader.readLines().size());
		Mat labels = new Mat(fileReader.readLines().size(), 1, opencv_core.CV_32SC1);
		IntBuffer labelsBuf = labels.createBuffer();
		for (int i = 0; i < fileReader.readLines().size(); i++) {
			String readLine =  fileReader.readLines().get(i);
			String[] result = readLine.split(";");
			if (result != null) {
				// images.push_back(opencv_imgcodecs.imread(result[0],opencv_imgcodecs.CV_LOAD_IMAGE_GRAYSCALE));
				Mat face = opencv_imgcodecs.imread(result[0], opencv_imgcodecs.CV_LOAD_IMAGE_GRAYSCALE);
				opencv_imgproc.resize(face, face, new Size(92, 112));
				images.put(i, face);
				// System.out.println(images);
				// labels.push_back(new
				// Mat(Integer.valueOf(result[1]),1,opencv_core.CV_32SC1));
				// //atoi函数将字符串转换为整数值
				labelsBuf.put(i,i);

			}
		}
		lists.add(images);
		lists.add(labels);
		return lists;
	}

	public static List<Object> read_csvs(String filename) {
		List<Object> lists = Lists.newArrayList();
		List<String> urls = Lists.newArrayList();
		File file = new File("F:\\images");
		for (int i = 0, total = file.listFiles().length; i < total; i++) {
			urls.add(file.listFiles()[i].getAbsolutePath());
		}
		MatVector images = new MatVector(urls.size());
		Mat labels = new Mat(urls.size(), 1, opencv_core.CV_32SC1);
		IntBuffer labelsBuf = labels.createBuffer();
		for (int i = 0; i < urls.size(); i++) {
			String p = urls.get(i);
			Mat img = opencv_imgcodecs.imread(p, opencv_imgcodecs.CV_LOAD_IMAGE_GRAYSCALE );
			images.put(i, img);
			labelsBuf.put(i, i);
		}
		lists.add(images);
		lists.add(labels);
		return lists;
	}

	public static void main(String[] argv) {
		// [1] 检测合法的命令，显示用法
		// 如果没有参数输入，则退出
		// if (argc < 2)
		// {
		// cout << "usage:" << argv[0] << "<csv.ext> <output_folder>" << endl;
		// exit(1);
		// }

		String output_folder;
		output_folder = "F:\\result";

		// [2] 读取CSV文件路径
		String fn_csv = "F:/att_faces/filename.txt";

		// 两个容器来存放图像数据和对应的标签

		// 读取数据，如果文件不合法就会出错。输入的文件名已经有了

		List<Object> list =  read_csv(fn_csv);//read_csvs(null);//
		MatVector images = (MatVector) list.get(0);
		Mat labels = (Mat) list.get(1);

		// 没有读取到足够多的图片，也需要退出
		// if (images.sizeof() <= 1) {
		// String error_message = "This demo need at least 2 images,please add
		// more images to your data set!";
		// }
		// [3] 得到第一张图片的高度，在下面对图像变形得到他们原始大小时需要
		int height = images.get(0).rows();
		// [4]下面代码仅从数据集中移除最后一张图片，用于做测试，需要根据自己的需要进行修改
		Mat testSample = opencv_imgcodecs.imread("F:\\188.jpg", opencv_imgcodecs.CV_LOAD_IMAGE_GRAYSCALE );

		String testLabel = labels.row(labels.rows() - 1).toString();

		// images.pop_back(); //删除最后一张图片
		// labels.pop_back(); //删除最后一个标签

		// [5] 创建一个特征脸模型用于人脸识别
		// 通过CSV文件读取的图像和标签训练它
		// 这里是一个完整的PCA 变换
		// 如果想保留10个主成分，使用如下代码 cv::createEigenFaceRecognizer(10);
		// 如果希望使用置信度阈值来初始化，使用代码 cv::createEigenFaceRecognizer(10, 123.0);
		// 如果使用所有特征并使用一个阈值，使用代码 cv::createEigenFaceRecognizer(0, 123.0);
		BasicFaceRecognizer model = EigenFaceRecognizer.create();

		model.train(images, labels);
		// [6] 对测试图像进行预测，predictedLabel是预测标签结果
		
		
		int predictedLabel = model.predict_label(testSample);

		// 还有一种调用方式，可以获取结果同时得到阈值:
		// int predictedLabel = -1;
		// double confidence = 0.0;
		// model->predict(testSample, predictedLabel, confidence);

		System.out.println(String.format("Predicted class = %d / Actual class = %s.", predictedLabel, testLabel));

		// [7] 如何获取特征脸模型的特征值例子，使用getEigenValues方法
		Mat eigenvalues = model.getEigenValues(); 
		// [8] 获取特征向量
		Mat W = model.getEigenVectors(); 
		// [9] 得到训练图像的均值向量
		Mat mean = model.getMean(); 
		// [10] 显示或保存
		// opencv_imgcodecs.imshow("mean", norm_0_255(mean.reshape(1,
		// images.get(0).rows())));
		opencv_imgcodecs.imwrite(String.format("%s/mean.png", output_folder),
				norm_0_255(mean.reshape(1, images.get(0).rows())));

		// [11] 显示或保存特征脸
		for (int i = 0; i <min(50, W.cols()); i++){
			// 修改数值10可以修改特征脸的数目
			System.out.println(String.format("Eigenvalue #%d = %.5f", i, eigenvalues.getFloatBuffer().get(i)));

			// 得到第i个特征向量
			Mat ev = W.col(i).clone();

			// 把它变成原始大小，把数据显示归一化到0-255
			Mat grayscale = norm_0_255(ev.reshape(1, height));

			// 使用伪彩色来显示结果，为了更好的观察
			Mat cgrayscale = new Mat();
			 
			opencv_imgproc.applyColorMap(grayscale, cgrayscale, opencv_imgproc.COLORMAP_JET);

			// 显示或保存

			// imshow(String.format("eigenface_%d", i), cgrayscale);

			opencv_imgcodecs.imwrite(String.format("%s/eigenface_%d.png", output_folder, i), norm_0_255(cgrayscale));
		}

		// [12] 预测过程中，显示或保存重建后的图像
		// 修改值300可改变重构的图像的数目 
		int begin = min(W.cols(), 10);
		int end = min(W.cols(),400); 
		 
		for (int num_components = begin; num_components < end; num_components += 15){
			// 从模型中的特征向量截取一部分
			Mat evs =new Mat(W, Range.all(),new Range(0, num_components));
			// 在重构时，images[0]为ORL人脸库的第一张人脸图，
			// 修改此数值0的大小可对其他人脸图像进行特征脸处理与重构的实验
			Mat projection = LDA.subspaceProject(evs, mean, images.get(0).reshape(1, 1));
			// 投影样本到LDA子空间
			Mat reconstruction = LDA.subspaceReconstruct(evs, mean, projection);
			// 重构来自于LDA子空间的投影
			// 归一化结果
			reconstruction = norm_0_255(reconstruction.reshape(1, images.get(0).rows()));
			// [13] 若不是存放到文件夹中就显示他，使用暂定等待键盘输入
			// imshow(String.format("eigenface_reconstruction_%d",
			// num_components),reconstruction);
			opencv_imgcodecs.imwrite(String.format("%s/eigenface_reconstruction_%d.png", output_folder, num_components),
					reconstruction);
		} 
	}
	
	/**
	 * 比较
	 * @param a
	 * @param b
	 * @return
	 */
	private static int min(int a,int b){
		return a <= b ? a : b;
	} 
	/**
	 *  归一化图像矩阵函数
	 * @param src
	 * @return
	 */
	public static Mat norm_0_255(Mat src) {
		Mat dst = new Mat();
		switch (src.channels()) {
		case 1:
			opencv_core.normalize(src, dst, 0d, 255d, opencv_core.NORM_MINMAX, opencv_core.CV_8UC1,null);
			break;
		case 3:
			opencv_core.normalize(src, dst, 0d, 255d, opencv_core.NORM_MINMAX, opencv_core.CV_8UC3,null);
			break;
		default:
			dst = new Mat(src); 
			break;
		}
		return dst;
	}

}
